/**
        @mainpage LOG550 Prototype 2
        @author Benoit Paquet\n Gabriel Kenderessy\n Aleksandre Clavet\n
        @file main.c
        @brief
                \n
                L'objectif de ce laboratoire est d'implémenter les requis suivants dans un microcontrôleur Atmel 32UC3AC512-U avec microC/OSII:\n
                Tout d'abord, chacune des tâches doivent être faites dans des threads différents et il ne faut plus utiliser d'interrupt.\n
                Le projet se divise en cinq tâches:\n
                 - LED_LCD(): \n
                   * Effectue le clignotement des LEDS au 500msec.\n
                   * Led1, clignote toujours des que votre programme démarre\n
                   * Led2, clignote lorsque l’acquisition est en service\n
                   * Led3, alarme, s’allume si le MessageQueue déborde et demeure allumé. \n
                   * Affiche sur l’écran LCD : la fréquence d’échantillonnage mesurée à chaque 500msec.\n
                   * A la ligne 1, la fréquence d’échantillonnage mesurée à chaque 500msec.\n
                   * A la ligne 2, le % d’utilisation du processeur mesuré à chaque 500msec.\n
                 - UART_Cmd_RX():\n
                   * Vérifie, au 200msec, si des commandes sont reçues par le UART.\n
                   * Si une commande est reçue, traiter celle-ci et envoyer l’ordre d’arrêt ou de démarrage à la tâche ADC_Cmd().\n
                 - UART_SendSample(): \n
                   * Vide le MessageQueue et envoi les échantillons vers le UART pour une transmission vers le PC.                  \n
                 - ADC_Cmd(): \n
                   * Cette tâche démarre les conversions, obtient les échantillons numérisés, et les place dans le MessageQ à la bonne vitesse d’acquisition.\n
                   * Si le MessageQueue est plein, envoi l’information à la tâche Alarm_MsgQ().\n
                 - Alarm_msgQ(): \n
                   * Cette tâche se réveille uniquement si le MessageQ déborde, et elle commande l’allumage du Led3 en informant la tache LED_LCD().\n
 */

#include "includes.h"
#include "gpio.h"
#include "led.h"
#include "usart.h"
#include "adc.h"
#include "print_funcs.h"
#include "delay.h"
#include "spi.h"
#include "dip204.h"
#include "pm.h"

#define OS_TASK_STK_SIZE 256
#define SAMPLE_QUEUE_SIZE 10
#define SAMPLING 's'
#define WAITING 'x'

OS_STK LED_LCD_Stk[OS_TASK_STK_SIZE];
OS_STK UART_Cmd_Stk[OS_TASK_STK_SIZE];
OS_STK UART_SendSample_Stk[OS_TASK_STK_SIZE];
OS_STK ADC_Cmd_Stk[OS_TASK_STK_SIZE];
OS_STK Alarm_msgQ_Stk[OS_TASK_STK_SIZE];

static void Alarm_msgQ(void *p_arg);
static void LED_LCD(void *p_arg);
static void UART_Cmd_RX(void *p_arg);
static void UART_SendSample(void *p_arg);
static void ADC_Cmd(void *p_arg);

OS_EVENT *OVERFLOW;
OS_EVENT *QUERY_MODE;
OS_EVENT *QUERY_RATE;
OS_EVENT *SAMPLE_READY;
OS_EVENT *SAMPLE_QUEUE;

U8 overflow_happened = 0;

U8 MODE = WAITING;
U16 RATE = 0;
void *queue[SAMPLE_QUEUE_SIZE];

void usart_init(void);
void adc_init(void);
void lcd_init(void);

/** @brief Fonction principale main()\n
 * */
int  main (void)
{
    CPU_IntDis();       /* Descative toute les interrupts pendant l'initialisation  */
    OSInit();           /* Initialise "uC/OS-II, The Real-Time Kernel"              */

    /* Creation de toute les taches...a des priorites differentes (5<prio<28) */
    OSTaskCreate(Alarm_msgQ, NULL, (OS_STK *)&Alarm_msgQ_Stk[OS_TASK_STK_SIZE-1], 6);
    OSTaskCreate(LED_LCD, NULL, (OS_STK *)&LED_LCD_Stk[OS_TASK_STK_SIZE-1], 7);
    OSTaskCreate(UART_Cmd_RX, NULL, (OS_STK *)&UART_Cmd_Stk[OS_TASK_STK_SIZE-1], 8);
    OSTaskCreate(UART_SendSample, NULL, (OS_STK *)&UART_SendSample_Stk[OS_TASK_STK_SIZE-1], 9);
    OSTaskCreate(ADC_Cmd, NULL, (OS_STK *)&ADC_Cmd_Stk[OS_TASK_STK_SIZE-1], 10);


    QUERY_MODE = OSSemCreate(1);
    QUERY_RATE = OSSemCreate(1);
    OVERFLOW = OSSemCreate(0);
    SAMPLE_QUEUE = OSQCreate(queue, SAMPLE_QUEUE_SIZE);

    /*
    SAMPLE_READY = OSSemCreate(0);

    */

    OSStart();          /* Demarre le multitasking (Donne le controle au noyau uC/OS-II)  */
    // Le code ici ne sera jamais execute
    return (0);         /* Pour eviter le warnings en GCC, prototype (int main (void))    */
}

/******************************************************************************************/
/* Definition des taches sous UCOS-II                                                     */
/*  - Toutes les taches ont une priorite differente                                       */
/*  - Le scheduler de UCOS-II execute TOUJOURS la tache la plus prioritaire qui est READY.*/
/******************************************************************************************/

/******************************************************************************************/

/** @brief Méthode en attente d'un overflow de messageQueue.\n
 * Le thread est pending tant qu'il n'y a pas de problème\n
 * Si un overflow est détecté, un post est fait et le thread débloque\n
 * */
static void Alarm_msgQ (void *p_arg)
{
    BSP_Init();           // Set le timer et demarre les ISR_TICKS.
						  // Doit absolument etre appele par la premiere tache a operer.
						  // Attention, desormais on roule a 48MHz et USART a 48/2MHz

	CPU_IntDis();		  // Desactive les IRQ pendant l'initialisation
    lcd_init();
	usart_init();
	adc_init();
	CPU_IntEn();		  // Reactive les IRQ

 /*---Fin de l'initialisation----------*/

	OSSemPend(OVERFLOW, 0, 0);
	overflow_happened = 1;

	while(1) {
		OSSemPend(OVERFLOW, 0, 0);
	}
}

/******************************************************************************************/

/** @brief Fait la gestion des LEDs et du LCD\n
 * Fait clignoter le premier LED\n
 * Si le board transfert des données, fait clignoter le deuxième LED\n
 * Si l'alarme d'overflow est levée, allume le troisième LED\n
 * 
 * ******** ATTENTION MONSIEUR LAROSE! ************
 * Nous n'avons pas été capable de faire l'affichage du LCD car un simple appel à la fonction sprintf fait geler tout le code
 * et nous n'avons pas été capable de le régler.  Le code d'affichage du LCD est donc en commentaire dans la méthode.
 * Au plaisir de ne plus vous revoir (en LOG550),
 * Merci et profitez du beau temps qui s'en vient!
 * ************************************************
 * */
static void LED_LCD (void *p_arg)
{
	U8 m = WAITING;
	U16 r = 0;
	char rateBuffer[10];
	char usageBuffer[50];

    while (1)
    {
    	OSSemPend(QUERY_MODE, 0, 0);
    	m = MODE;
    	OSSemPost(QUERY_MODE);

		OSSemPend(QUERY_RATE, 0, 0);
		r = RATE;
		OSSemPost(QUERY_RATE);

		//dip204_clear_display();

		if (overflow_happened == 1) {
			LED_Toggle(LED2);
			overflow_happened = 0;
		}

    	if(m == SAMPLING)
		{
    		LED_Toggle(LED1);
    		/*dip204_set_cursor_position(1,1);
    		sprintf(rateBuffer, "Rate: %4d", r * 2);
            dip204_set_cursor_position(1,1);
            sprintf(usageBuffer, "CPU Usage: %3u%%", OSCPUUsage);
            dip204_write_string(usageBuffer);*/
		}
    	else
    	{
            dip204_set_cursor_position(1,1);
            dip204_write_string("Waiting for samples");
    	}

    	//Toujours faire flasher la LED1
    	LED_Toggle(LED0);

    	OSTimeDly(500);
    }
}
/******************************************************************************************/

/** @brief Fait la réception des données sur le port sériel\n
 * */
static void UART_Cmd_RX (void *p_arg)
{
    while (1)
	{
    	if (AVR32_USART1.csr & (AVR32_USART_CSR_RXRDY_MASK))
		{
    		OSSemPend(QUERY_MODE, 0, 0);
    		MODE = (AVR32_USART1.rhr & AVR32_USART_RHR_RXCHR_MASK);
    		OSSemPost(QUERY_MODE);
		}
    	OSTimeDly(200);
	}
}
/******************************************************************************************/

/** @brief Envoie les échantillons de lumière et de potentiomètre sur le port sériel\n
 * Les données proviennent d'une messageQueue\n
 * */
static void UART_SendSample (void *p_arg)
{
    (void)p_arg;
	U8 err;
	U8 m;

    while (1)
    {
    	//OSTimeDly(200);
    	OSSemPend(QUERY_MODE, 0, 0);
    	m = MODE;
    	OSSemPost(QUERY_MODE);

    	if(m == SAMPLING)
    	{
    		usart_write_char(&AVR32_USART1, *((U8 *)OSQPend(SAMPLE_QUEUE, 0, &err)));

    		OSSemPend(QUERY_RATE, 0, 0);
    		RATE++;
    		OSSemPost(QUERY_RATE);
    	}
    }
}
/******************************************************************************************/

/** @brief Démarre l'échantillonnage de la lumière et du potentiomètre\n
 * Détecte si la messageQueue est pleine.  Si c'est le cas, fais un post sur le \n
 * sémaphore qui bloque le thread d'alarme.\n
 * */
static void ADC_Cmd (void *p_arg)
{
    (void)p_arg;
    U8 err;
    U8 m;
	U8 sample;

    while (1)
    {
    	OSSemPend(QUERY_MODE, 0, 0);
    	m = MODE;
    	OSSemPost(QUERY_MODE);

    	if(m == SAMPLING)
    	{
    		adc_start(&AVR32_ADC);
            sample = ((adc_get_value(&AVR32_ADC, ADC_POTENTIOMETER_CHANNEL) >> 3) << 1);
        	if(OSQPost(SAMPLE_QUEUE, &sample) == OS_Q_FULL) {
        		OSSemPost(OVERFLOW);
        	}
        	sample = ((adc_get_value(&AVR32_ADC, ADC_LIGHT_CHANNEL) >> 3) << 1) | 1;
            if(OSQPost(SAMPLE_QUEUE, &sample) == OS_Q_FULL) {
            	OSSemPost(OVERFLOW);
            }
    	}
    }
}
/******************************************************************************************/

/** @brief Initialisation du lcd \n
 * */
void lcd_init()
{
        static const gpio_map_t DIP204_SPI_GPIO_MAP =
        {
			{DIP204_SPI_SCK_PIN,  DIP204_SPI_SCK_FUNCTION },  // SPI Clock.
			{DIP204_SPI_MISO_PIN, DIP204_SPI_MISO_FUNCTION},  // MISO.
			{DIP204_SPI_MOSI_PIN, DIP204_SPI_MOSI_FUNCTION},  // MOSI.
			{DIP204_SPI_NPCS_PIN, DIP204_SPI_NPCS_FUNCTION}   // Chip Select NPCS.
        };

        // add the spi options driver structure for the LCD DIP204
        spi_options_t spiOptions =
        {
			.reg          = DIP204_SPI_NPCS,
			.baudrate     = 1000000,
			.bits         = 8,
			.spck_delay   = 0,
			.trans_delay  = 0,
			.stay_act     = 1,
			.spi_mode     = 0,
			.modfdis      = 1
        };

        // Assign I/Os to SPI
        gpio_enable_module(DIP204_SPI_GPIO_MAP, sizeof(DIP204_SPI_GPIO_MAP) / sizeof(DIP204_SPI_GPIO_MAP[0]));

        // Initialize as master
        spi_initMaster(DIP204_SPI, &spiOptions);

        // Set selection mode: variable_ps, pcs_decode, delay
        spi_selectionMode(DIP204_SPI, 0, 0, 0);

        // Enable SPI
        spi_enable(DIP204_SPI);

        // setup chip registers
        spi_setupChipReg(DIP204_SPI, &spiOptions, FOSC0);

        // initialize delay driver
        delay_init( FOSC0 );

        // initialize LCD
        dip204_init(backlight_PWM, TRUE);
}

/** @brief Initialisation de l'usart \n
 * */
void usart_init(void)
{
    static const gpio_map_t USART_GPIO_MAP =
     {
       {AVR32_USART1_RXD_0_0_PIN, AVR32_USART1_RXD_0_0_FUNCTION},
       {AVR32_USART1_TXD_0_0_PIN, AVR32_USART1_TXD_0_0_FUNCTION}
     };
    // Assigner les pins du GPIO a etre utiliser par le USART1.
    gpio_enable_module(USART_GPIO_MAP,sizeof(USART_GPIO_MAP) / sizeof(USART_GPIO_MAP[0]));

    static const usart_options_t USART_OPTIONS =
    {
            .baudrate = 57600,
            .charlength = 8,
            .paritytype = USART_NO_PARITY,
            .stopbits = USART_1_STOPBIT,
            .channelmode = USART_NORMAL_CHMODE
    };

    usart_init_rs232(&AVR32_USART1, &USART_OPTIONS, FOSC0/2);
}

/*!
 * \brief Initialisation ADC : Potentiomètre et capteur de lumière
 * Initialise le composant ADC qui gère les valeurs réels (analogique) du potentiomètre et du capteur de lumière
*/
void adc_init(void)
{
	pm_switch_to_osc0(&AVR32_PM, FOSC0, OSC0_STARTUP);

    // GPIO pin/adc-function map.
    static const gpio_map_t ADC_GPIO_MAP =
    {
            {AVR32_ADC_AD_2_PIN, AVR32_ADC_AD_2_FUNCTION},
            {AVR32_ADC_AD_1_PIN, AVR32_ADC_AD_1_FUNCTION}
    };

    // Assign and enable GPIO pins to the ADC function.
    gpio_enable_module(ADC_GPIO_MAP, sizeof(ADC_GPIO_MAP) / sizeof(ADC_GPIO_MAP[0]));

    adc_configure(&AVR32_ADC);

    adc_enable(&AVR32_ADC, ADC_LIGHT_CHANNEL);
    adc_enable(&AVR32_ADC, ADC_POTENTIOMETER_CHANNEL);
}
